Tämä artikkeli on tekninen lisäosa kolmen artikkelin Sujuvaa opinnäytetyöprosessia rakentamassa Y-Akatemiassa –sarjaan (Balerina & Gröhn 2025a; 2025b; 2025c) Artikkeli on kirjoitettu ohjelmointia osaaville lukijoille, ja käsittelemme asioita puhtaasti teknisen toteutuksen näkökulmasta. Alkupuolella kuvataan, mitä artikkelisarjassa yleisemmällä tasolla mainittujen asioiden toteuttaminen vaati Robot Frameworkin sekä muunnosskriptien osalta. Jälkimmäinen osa keskittyy raakadatan muuntamiseen sekä yhdistelyyn, että saamme aikaan hyödyllistä tietoa visualisoinnin kannalta. Aputaulukoiksi yhdistäminen tapahtuu DAX-kielen UNION-, NATURALLEFTOUTERJOIN- ja SUMMARIZE –funktioiden avulla.
Wikisivun lataaminen palvelimelle automatisoidusti
Sujuvaa opinnäytetyöprosessia rakentamassa Y-Akatemiassa -sarjan kolmannessa osassa todettiin, että koska kaikki työtilat eivät sisällä samantyyppistä Wikisivua, on syytä kohdistaa datan noutaminen vain tiettyihin kursseihin. Se ratkaistiin hyödyntämällä Robot Frameworkin Import Resource -käskyä.
Käytännössä tämä tapahtuu seuraavasti. Määrittelemme uuden komennon hakuskriptiin, joka pyrkii suorittamaan työtilakohtaisen toiminnon Execute Custom Task –komentoa kutsumalla. Jos työtilan tunnisteen mukainen tiedosto löytyy ennalta määritetystä paikasta, se ladataan Import Resource -komennolla ja varmistetaan että se sisältää Execute Custom Task –komennon. Jos komento on saatu ladattua, se suoritetaan.
Execute Custom Tasks
[Arguments] ${Id}
${resourcefile}= Set Variable ${CustomScriptDir}/${Id}.resource
# check do we have custom code and run if so
${hasResourceFile}= Run Keyword And Return Status File Should Exist ${resourcefile}
IF ${hasResourceFile}
# Import resource uses filename prefix as separator for similarly named keywords
${resourceOk}= Run Keyword And Return Status Import Resource ${resourcefile}
${ExecuteCustomTask}= Set Variable ${Id}.Execute Custom Task
${keywordOk}= Run Keyword And Return Status Keyword Should Exist ${ExecuteCustomTask}
IF ${resourceOk} == False
Log Resource ${resourcefile} could not be read. WARN
ELSE IF ${keywordOk} == False
Log Resource ${resourcefile} did not contain keyword Execute Custom Script. WARN
ELSE
Run Keyword ${ExecuteCustomTask} ${Id}
END
END
Execute Custom Tasks -toiminnon avulla on mahdollista antaa kullekin kurssille omia tehtäviä, jolloin erityispiirteet oppimisanalytiikan kannalta saadaan paremmin huomioitua.
Wikisivusta CSV-tiedostoksi
Prosessin seuraava vaihe suoritetaan kuoriskriptillä, joka noutaa ja muuntaa Moodlen Wikisivulle merkityn taulukon palvelimelle CSV-muodossa. Artikkelissa mainittiin, että ratkaisussa käytettiin awk-, sed-, ja tr-komentorivityökalua.
Skriptin toiminta lähtee syötetyn wikisivun tekstin syöttämisestä AWK-komennolle, joka rajaa tulostettavan HTML-sivun sisällön <tbody> -tagien sisältöön, ja syöttää tulosteen edelleen sed-komennolle.
InFile=$1
OutFile=$2
cat ${InFile} |
awk ' $1 ~ /" { tmp=0;}'|
Sed-komento puolestaan vaihtaa kaikki HTML-koodatut välilyöntimerkit oikeiksi välilyönneiksi, sekä vaihtaa rivinvaihtotagin XML-määrityksen mukaiseksi.
sed -e 's/ / /g' -e 's/<br>/<br \/>/g' |
Seuraavaksi sed-komennolla suoritetaan useampia ketjutettu komento, jotka suoritetaan käsiteltävälle riville aina riippumatta niiden järjestyksestä. Käsittelemme dataa säännöllisten lausekkeiden avulla, joten sed-komennolle annetaan optio “-r”.
Ensimmäisenä määritetään sääntö, joka vaihtaa & -merkkijonot niitä vastaaviksi &-merkeiksi. Tämä tapahtuu käytännössä poistamalla ainoastaan osa “amp;”. Seuraava sääntö tulostaa <td>-tagien sisälle kirjoitetut nimet lainausmerkkien sisään, ja liittää loppuun pilkun. Mukaan otetaan myös mahdollinen &-merkki. Moodlen automaattinen linkitys lisää lataustiedostoon välillä uuden <a>-tagin, joka poistuu samalla.
sed -r \
-e 's/amp;//g' \
-e 's/()?([&|[:alnum:]|[:blank:]|,|-]+)(<\/a>)?<\/td>/"\2",/g' \
Seuraava sääntö etsii kuukauden ja vuoden samalta riviltä, ja tulostaa sen lainausmerkkien sisään liittäen loppuun pilkun.
-e
's/([[:alnum:]]+[[:blank:]]?[[:alnum:]]+)[[:blank:]]+/"\1",/g' \
Alla oleva sääntö siirtää otsikkorivin sarakkeet lainausmerkkien sisään, poistaa niistä <strong>-tagit, ja lisää loppuun pilkun.
-e 's/([[:alnum:]]+[[:blank:]]?[[:alnum:]]+)[[:blank:]]+</strong></td>/"\1",/g' \
Loput säännöistä liittyvät lähinnä lopputuloksen siistimiseen. Ensimmäinen näistä poistaa aloittavan <tbody>-, sekä sulkevan </tbody>-tagin lopputuloksesta.
-e 's/(<tbody>[[:space:]]*|[[:space:]]+$|[[:space:]]+</tbody>)//g' \
Seuraava sääntö poistaa kaikki tyhjät taulukon sarakkeet.
-e 's/(<td>.*<\/td>)|(<td[[:space:]]\/>)//g' |
Sen jälkeen syöte ohjataan tr-ohjelmalle, jonka avulla rivit yhdistetään, ja riviltä poistetaan kaikki <tr>-tagit, joiden sisällä ei ole mitään.
tr -d '\n' |
sed -r -e 's/[[:blank:]][[:blank:]]//g' \
Käsittelyn myötä tulosteeseen saattaa jäädä <tr>-tageja, jonka sisällä on ainoastaan pilkku ja mahdollinen välilyönti, ja sulkevan </tr>-tagin jälkeen voi tulla toinen samanlainen. Nämä yhdistetään pelkästään yhdeksi </tr>-tagiksi.
-e 's/,[[:blank:]]+</tr>(</tr>)?/</tr>/g' \
Alla on pilkkujen jälkeiset tyhjemerkit poistava sääntö, sekä </tr> -tagit rivinvaihtomerkeiksi muuttava sääntö ja tulosteen uudelleenohjaus tulostustiedostoon.
-e 's/",([[:blank:]]+)"/","/g' \
-e 's//\n/g' > ${OutFile}
Kun kokonaisuutta tarkastellaan pilkotut osat yhdistämällä, lopputulos voi näyttää jopa hieman pelottavalta.
sed -r \
-e 's/amp;//g' \
-e 's/()?([&|[:alnum:]|[:blank:]|,|-]+)(<\/a>)?<\/td>/"\2",/g' \ -e 's/([[:digit:]]+\/[[:digit:]]{4})<\/td>/"\1",/g' \ -e 's/([[:alnum:]]+[[:blank:]]?[[:alnum:]]+)[[:blank:]]+/"\1",/g' \
-e 's/([[:space:]]|[[:space:]]+$|[[:space:]]+)//g' \
-e 's/(.<\/td>)|()//g' \
tr -d '\n' |
sed -r -e 's/[[:blank:]][[:blank:]]//g' \
-e 's/,[[:blank:]]+()?//g' \
-e 's/",([[:blank:]]+)"/","/g' \
-e 's//\n/g' > ${OutFile}
Skriptin avulla saadaan helposti tietoa CSV-muotoon, joka puolestaan saadaan luettua varsin yksinkertaisesti Power BI Desktopissa. Seuraavaksi vuorossa ovat Power BI Desktopin vaatimat aputaulukot.
Kaikki tehtävät ja Wihin suoritukset yhdisteenä
Yhdiste (UNION) on funktio, joka liittää kahden taulukon rivit yhteen. Taulukoissa on oltava sama määrä sarakkeita, samassa järjestyksessä. UNION palauttaa yhdisteen sarakkeiden osalta, vaikka ne eivät olisikaan samannimisiä, ja valitsee sarakkeiden nimiksi ensimmäisen taulukon sarakkeiden nimet.
Yhdistettä on käytetty esimerkiksi seuraavassa Y-Akatemian aputaulukon luomisessa:
Kaikki tehtävät ja Wihisuoritukset =
VAR
_wihinTehtavat = SELECTCOLUMNS(VALUES('opinnäytetyön tila'[ToteutusNimi]), "Tehtävä", 'opinnäytetyön tila'[ToteutusNimi], "id", BLANK(), "duedate", BLANK(), "osio", 1)
RETURN
UNION('Kaikki tehtävät',_wihinTehtavat)
DAX-kyselssä luomme taulukon “Kaikki tehtävät ja Wihisuoritukset”, johon haetaan kaikki tehtävät -taulukon rivit (koostuen sarakkeista tehtävä, id, duedate ja osio) ja yhdistetään _wihinTehtavat –taulukon rivien kanssa.
_wihinTehtavat –taulukko rakennetaan SELECTCOLUMNS-funktion avulla. Sillä voidaan rakentaa muutettu taulukko jostain olemassa olevasta taulukosta. Muutettuun taulukkoon voidaan esimerkiksi valita pelkästään tietyt sarakkeet, lisätä uusia sarakkeita, tai vaihtaa olemassa olevien sarakkeiden nimiä ja järjestystä.
Tässä tapauksessa sille välitetään VALUES-funktion avulla taulukko, joka on sisältää yhden sarakkeen, jossa on yksilölliset arvot Opinnäytetyön tila-taulukon ToteutusNimi-sarakkeesta – eli samaa arvoa ei ole mukana kahdesti. Lisäksi _wihinTehtavat-taulukkoon liitetään kaksi tyhjää arvoa sisältävää sarakketta (id sekä duedate) ja lisätään osio-sarake, jolle asetetaan arvo 1.
Opinnäytetyöprosessin palautukset vasemmalla ulkoliitoksella
Vasen ulkoliitos (NATURALLEFTOUTERJOIN) on menetelmä liittää oikeanpuoleisen taulun sarakkeita vasemmanpuoleisen taulun kanssa rivikohtaisesti. Ehtona on, että oikeanpuoleisen taulukon riveillä on samat samannimisten sarakkeiden arvot. Tuloksena palautuu taulukko, jossa on kaikki rivit vasemmanpuoleisesta taulukosta, ja niiden perässä oikeanpuoleisen taulukon sisältämät arvot, jotka kohdistuvat samannimisten sarakkeiden kautta kyseiseen riviin.
Vasenta ulkoliitosta hyödynnetään muun muassa Y-Akatemian opinnäytetyöprosessin visualisoinnin “Opinnäytetyöprosessin palautukset” –taulukon luomisessa. Siinä yhdstämme opiskelijan palautukset Moodlessa, suoritukset Wihissä ja opinnäytetyön nimet.
Opinnäytetyöprosessin palautukset =
VAR
_moodlenTehtavat = SELECTCOLUMNS(FILTER(Activity, Activity[verb_name] = "scored"), "Email", CONCATENATE(Activity[Email],""), "Opiskelija", CONCATENATE(Activity[Opiskelija],""), "pvm", Activity[aika]+0,"Tehtävä", CONCATENATE(Activity[Tehtava],""))
VAR
_wihinTehtavat = SELECTCOLUMNS('opinnäytetyön tila', "Email", CONCATENATE('opinnäytetyön tila'[Email],""), "Opiskelija", CONCATENATE('opinnäytetyön tila'[Opiskelijanimi],""), "pvm", 'opinnäytetyön tila'[ArviointiPvm]+0,"Tehtävä",CONCATENATE('opinnäytetyön tila'[ToteutusNimi],""))
VAR
_emailOpinnaytetyot = DISTINCT(SELECTCOLUMNS(ryhmittelyt, "Email", CONCATENATE(ryhmittelyt[Email],""), "Opinnäytetyön nimi", ryhmittelyt[opinnäytetyön nimi]))
RETURN
UNION(NATURALLEFTOUTERJOIN(_emailOpinnaytetyot,_moodlenTehtavat), NATURALLEFTOUTERJOIN(_emailOpinnaytetyot, _wihinTehtavat))
Kuten listauksesta on havaittavissa, tämä tapahtuu useammassa vaiheessa. Ensiksi muodostamme Moodlen tehtävistä taulukon _moodlenTehtavat, joissa näkyy sähköposti, opiskelijan nimi, palautuspäivä sekä tehtävän nimi. Tämän jälkeen muodostamme taulukon _wihinTehtavat Wihin suorituksista, jotka kuvataan vastaavan nimisillä sarakkeilla Moodlen tehtäväpalautustaulukkoon nähden, että voimme luoda vasemman ulkoliitoksen. Kolmannessa vaiheessa haemme ryhmittelyt –taulukosta opiskelijan sähköpostiosoitteen, sekä opinnäytetyön nimen taulukkoon _emailOpinnaytetyot. Sen jälkeen on mahdollista tehdä vasemmat ulkoliitokset _emailOpinnaytetyot + _moodlenTehtavat, _emailOpinnaytetyot + _wihinTehtavat. Kaksi välitulosta liitetään yhteen aiemmin läpikäydyllä yhdistefunktiolla muodostaen taulukon, josta saadaan selville kaikki palautustiedot opinnäytetyöprosessin osalta.
NATURALLEFTOUTERJOIN-operaatioiden tekemisessä on syytä muistaa, että yhdistettävillä sarakkeilla ei saa olla keskenään riippuvuuksia. Tämän ratkaisemiseen yksi keino on liittää numeeristen arvojen yhteyteen laskutoimitus + 0, ja tekstimuotoisten sarakkeiden yhteyteen CONCATENATE(sarake,””). Nämä käytännössä purkavat mahdolliset riippuvuudet JOIN-operaatioita varten, mutta säilyttävät ne kuitenkin muuta mahdollista käyttöä varten.
Aputaulukko opiskelijoiden edistymistä varten
Tämä oli eräs jännittävimmistä tehtävistä visualisoinnin osalta. Siinä pyritään luomaan taulukko, jossa on opinnäytetyökohtaiset maksimipisteet kullekin opiskelijalle.
OpparitehtavienPisteetRyhmittain =
SUMMARIZE(
NATURALLEFTOUTERJOIN(
SELECTCOLUMNS('All Scores',"Email",CONCATENATE('All Scores'[Email],""), "TehtäväID", 'All Scores'[tehtava_id], "Tehtävä", 'All Scores'[Tehtävä],"Pisteet value",'All Scores'[Pisteet Value]),
DISTINCT(
SELECTCOLUMNS(ryhmittelyt,"Email",CONCATENATE(ryhmittelyt[Email],""),"oppari",ryhmittelyt[opinnäytetyön nimi]))
)
,
[Tehtävä],
[oppari],
[TehtäväID],
"Maxpisteet", MAX('All Scores'[Pisteet Value])
)
Ensimmäisessä vaiheessa vasen ulkoliitos kaikkien tehtävien sekä ryhmittelyjen osalta, että tehtävän nimen yhteyteen saadaan sidottua opinnäytetyön nimi kullekin opiskelijalle. Syntynyt taulukko annetaan parametrina SUMMARIZE-funktiolle, jonka avulla voidaan laskea halutun laskukaavan avulla esimerkiksi maksimiarvot, kun rivit on ryhmitelty haluttujen kenttien avulla.
Syntyneestä taulukosta valitaan tehtävän nimi, opinnäytetyön nimi, tehtävän ID-tunniste, sekä lasketaan maksimipisteet kaikille tehtävä-opinnäytetyöpareille.
Edistymisen mittarin toteutus
Tämän jälkeen on mahdollista laskea yhteen pisteet hyväksytysti suoritetuista tehtävistä. Koska tehtävän arvioinnissa on käytössä asteikko ei arviointia – hylätty – täydennettävä – hyväksytty, on asteikon “pistearvot” tallennettu kokonaislukuina (-1) – 0 – 1 –2. Pisteiden laskeminen tapahtuu seuraavan mittarin avulla:
Tehtäviä suoritettu LKM =
VAR
thesis = MIN(ryhmittelyt[opinnäytetyön nimi])
RETURN
CALCULATE(SUMX(FILTER(OpparitehtavienPisteetRyhmittain, OpparitehtavienPisteetRyhmittain[Maxpisteet] = 2 && OpparitehtavienPisteetRyhmittain[oppari] = thesis && ISBLANK(OpparitehtavienPisteetRyhmittain[oppari]) = FALSE() ), OpparitehtavienPisteetRyhmittain[Maxpisteet]) ) / 2
Mittarissa valitaan ensimmäisenä opinnäytetyön nimi erilliseen muuttujaan. Tämä on mahdollista, silä ryhmittelyjen ja opiskelijatietojen välille on rakennettu Power BI:ssä riippuvuus.
Varsinainen laskutoimitus on toteutettu CALCULATE-funktion avulla, joka laskee rivikohtaisen summan halutulla laskukaavalla. Tässä tapauksessa se on MaxPisteet / 2, koska hyväksytyn tehtävän pistemäärä on aina 2, mutta haluamme huomioida aina yhden pisteen jokaisesta hyväksytystä tehtävästä. Suodattimella (FILTER) rajataan käsiteltävät rivit sellaisiksi, että niiden maksimipisteet on 2, opinnäytetyönä on aiemmin haettu nimi, joka ei ole tyhjä.
Silloin saadaan oikea pistemäärä suhteessa tehtävien lukumäärään, ja voimme esittää etenemisen prosentuaalisesti visualisoinnissa toisen mittarin avulla:
Eteneminen = [Tehtäviä suoritettu LKM] / [Tehtävien Lkm]
Selvittiinhän siitä!
Oppimisanalytiikan kehittäminen vaatii välillä uusien asioiden opiskelua ongelmanratkaisun tueksi. Esimerkiksi Robot Frameworkin osalta osaaminen kehittyi jo hyvin yksityskohtaiselle tasolle toteutettuanit Import Resource –toimintoon nojaavan tuen kurssikohtaisesti muokattaville skripteille taustapalveluun.
CSV-muotoisen datan tuottaminen sinällään ei vaatinut enää uusien asioiden haltuunottoa, lähinnä vaan raakaa työtä säännöllisten lausekkeiden määrittämisessä halutun lopputuloksen saavuttamiseksti.
Power BI Desktopin osalta sen sijaan työkalun toiminnot tulivat entistä tutummiksi aputaulukoiden ja niissä käytettyjen funktioiden myötä. Aiemmin vain harvakseltaan tarvittuja funktioita kykenee nyt käyttämään entistä tehokkaammin. Tietojenkäsittelijän ja ohjelmoijan näkökulmasta uusien asioiden haltuunotto on ollut jopa hauskaa ja hyödyllistä – varsinkin kun lopputulos hyödyttää myös toimeksiantajaa. Ratkaisut eivät kuitenkaan ole varmasti vielä optimaalisia, ja helpompiakin tapoja lienee – joten kehitystyö jatkukoon.
Kirjoittaja:
Anssi Gröhn, lehtori, Karelia-ammattikorkeakoulu
Lähteet:
Balerina, M. & Gröhn, A. 2025a. Sujuvaa opinnäytetyöprosessia rakentamassa Y-Akatemiassa: osa 1. https://www.karelia.fi/2025/03/sujuvaa-opinnaytetyoprosessia-rakentamassa-y-akatemiassa-osa-1/ 11.3.2025.
Balerina, M. & Gröhn, A. 2025b. Sujuvaa opinnäytetyöprosessia rakentamassa Y-Akatemiassa: osa 2. https://www.karelia.fi/2025/04/sujuvaa-opinnaytetyoprosessia-rakentamassa-y-akatemiassa-osa-2. 8.4.2025.
Balerina, M. & Gröhn, A. 2025c. Sujuvaa opinnäytetyöprosessia rakentamassa Y-Akatemiassa: osa 3. https://www.karelia.fi/2025/04/sujuvaa-opinnaytetyoprosessia-rakentamassa-y-akatemiassa-osa-3/ 22.4.2025.